//SPDX-FileCopyrightText: Copyright 2022-2024 深圳市同心圆网络有限公司
//SPDX-License-Identifier: GPL-3.0-only

import React, { useCallback, useEffect, useRef, useState } from 'react';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { DndProvider } from 'react-dnd';
import { observer } from 'mobx-react';
import ReactFlow, { Background, Controls, Panel, ReactFlowProvider, Node as FlowNode, Edge as FlowEdge, XYPosition, OnNodesChange, NodePositionChange, NodeDimensionChange, OnConnect, MarkerType, ConnectionMode } from 'reactflow';
import { useSize } from 'ahooks';
import { useDataViewStores } from './store';
import { DND_ITEM_TYPE, NODE_TYPE_BASE_GROUP, create_edge, create_node, update_node_position, update_node_size } from '@/api/project_dataview';
import { type DropTargetMonitor, useDrop } from "react-dnd";
import type { EdgeInfo, NodeInfo } from "@/api/project_dataview";
import ProjectNodeListPanel from './compoents/project_nodes/ProjectNodeListPanel';
import type { DragNodeInfo } from './compoents/project_nodes/ProjectDragListNode';
import { get_session } from '@/api/user';
import { request } from '@/utils/request';
import { listen } from '@tauri-apps/api/event';
import type * as NoticeType from '@/api/notice_type';
import { allNodeTyps, getNodeTypeStr } from './compoents/project_nodes/helper';
import LabelEdge from './compoents/project_nodes/LabelEdge';
import ProjectMemberListPanel from './compoents/project_nodes/ProjectMemberListPanel';
import ChatPanel from './compoents/project_nodes/ChatPanel';

const ProjectDataViewEditor = observer(() => {
    const dataViewStore = useDataViewStores();

    const divRef = useRef<HTMLDivElement>(null);
    const divSize = useSize(divRef);

    const [nodes, setNodes] = useState<FlowNode<NodeInfo>[]>([]);
    const [edges, setEdges] = useState<FlowEdge<EdgeInfo>[]>([]);


    const createNode = async (itemData: DragNodeInfo, position: XYPosition) => {
        if ((dataViewStore.projectStore.viewInfo?.user_perm.can_update ?? false) == false) {
            return;
        }
        const sessionId = await get_session();
        await request(create_node({
            session_id: sessionId,
            project_id: dataViewStore.projectStore.projectId,
            view_id: dataViewStore.projectStore.dataViewId,
            node_type: itemData.nodeType,
            x: Math.round(position.x),
            y: Math.round(position.y),
            w: itemData.defaultWidth,
            h: itemData.defaultHeight,
            bg_color: "white",
            node_data: itemData.nodeData,
        }));
    };

    const updateNodePosition = async (nodeId: string) => {
        if ((dataViewStore.projectStore.viewInfo?.user_perm.can_update ?? false) == false) {
            return;
        }
        const node = dataViewStore.projectStore.getNode(nodeId);
        if (node == undefined) {
            return;
        }
        const sessionId = await get_session();
        await request(update_node_position({
            session_id: sessionId,
            project_id: dataViewStore.projectStore.projectId,
            view_id: dataViewStore.projectStore.dataViewId,
            node_id: nodeId,
            x: node.x,
            y: node.y,
        }));
    }

    const updateNodeSize = async (nodeId: string) => {
        if ((dataViewStore.projectStore.viewInfo?.user_perm.can_update ?? false) == false) {
            return;
        }
        const node = dataViewStore.projectStore.getNode(nodeId);
        if (node == undefined) {
            return;
        }
        const sessionId = await get_session();
        await request(update_node_size({
            session_id: sessionId,
            project_id: dataViewStore.projectStore.projectId,
            view_id: dataViewStore.projectStore.dataViewId,
            node_id: nodeId,
            w: node.w,
            h: node.h,
        }));
    };

    const createEdge = async (fromNodeId: string, fromHandleId: string, toNodeId: string, toHandleId: string) => {
        if ((dataViewStore.projectStore.viewInfo?.user_perm.can_update ?? false) == false) {
            return;
        }
        const sessionId = await get_session();
        await request(create_edge({
            session_id: sessionId,
            project_id: dataViewStore.projectStore.projectId,
            view_id: dataViewStore.projectStore.dataViewId,
            edge: {
                edge_key: {
                    from_node_id: fromNodeId,
                    from_handle_id: fromHandleId,
                    to_node_id: toNodeId,
                    to_handle_id: toHandleId,
                },
                label: "",
            },
        }));
    };

    const [_, drop] = useDrop(() => ({
        accept: DND_ITEM_TYPE,
        drop: (itemData: DragNodeInfo, monitor: DropTargetMonitor<DragNodeInfo, void>) => {
            if (dataViewStore.projectStore.flowInstance != null) {
                const clientOffset = monitor.getClientOffset();
                const position = dataViewStore.projectStore.flowInstance.screenToFlowPosition({
                    x: (clientOffset?.x ?? 0),
                    y: (clientOffset?.y ?? 0),
                });
                createNode(itemData, position);
            }
        },
        canDrop: () => true,
    }));

    const onNodesChange: OnNodesChange = useCallback(
        (changes) => {
            if ((dataViewStore.projectStore.viewInfo?.user_perm.can_update ?? false) == false) {
                return;
            }
            for (const change of changes) {
                if (change.type == "position") {
                    const realChange = change as NodePositionChange;
                    if (realChange.dragging) {
                        dataViewStore.projectStore.updateNodePosition(change.id, Math.round(realChange.position?.x ?? 0), Math.round(realChange.position?.y ?? 0));
                    } else {
                        updateNodePosition(change.id);
                    }
                } else if (change.type == "dimensions") {
                    const realChange = change as NodeDimensionChange;
                    if (realChange.resizing) {
                        dataViewStore.projectStore.updateNodeSize(change.id, Math.round(realChange.dimensions?.width ?? 0), Math.round(realChange.dimensions?.height ?? 0));
                    } else if (realChange.resizing == false) {
                        updateNodeSize(change.id);
                    }
                }
            }
        },
        [setNodes]
    );


    const onConnect: OnConnect = useCallback(
        (conn) => {
            if ((dataViewStore.projectStore.viewInfo?.user_perm.can_update ?? false) == false) {
                return;
            }
            if (conn.source == conn.target || conn.source == null || conn.target == null || conn.sourceHandle == null || conn.targetHandle == null) {
                return;
            }
            createEdge(conn.source, conn.sourceHandle, conn.target, conn.targetHandle);
        },
        [setEdges]
    );

    useEffect(() => {
        const tmpNodeList: FlowNode<NodeInfo>[] = [];
        for (const node of dataViewStore.projectStore.nodeList) {
            tmpNodeList.push({
                id: node.node_id,
                data: node,
                position: {
                    x: node.x,
                    y: node.y,
                },
                width: node.w,
                height: node.h,
                type: getNodeTypeStr(node.node_type),
                selectable: false,
                zIndex: node.node_type == NODE_TYPE_BASE_GROUP ? 10 : 100,
            });
        }
        setNodes(tmpNodeList);

        const tmpEdgeList: FlowEdge<EdgeInfo>[] = [];
        for (const edge of dataViewStore.projectStore.edgeList) {
            tmpEdgeList.push({
                id: `${edge.edge_key.from_node_id}:${edge.edge_key.from_handle_id}:${edge.edge_key.to_node_id}:${edge.edge_key.to_handle_id}`,
                source: edge.edge_key.from_node_id,
                target: edge.edge_key.to_node_id,
                sourceHandle: edge.edge_key.from_handle_id,
                targetHandle: edge.edge_key.to_handle_id,
                data: edge,
                animated: true,
                type: "LabelEdge",
                zIndex: 1000,
                markerEnd: {
                    type: MarkerType.Arrow,
                    color: 'orange',
                    strokeWidth: 2,
                },
            });
        }
        setEdges(tmpEdgeList);
    }, [dataViewStore.projectStore.nodeList, dataViewStore.projectStore.edgeList]);

    useEffect(() => {
        dataViewStore.projectStore.flowInstance?.fitView();
    }, [divSize?.height, divSize?.width]);

    useEffect(() => {
        const unListenFn = listen<NoticeType.AllNotice>("notice", ev => {
            const notice = ev.payload;
            if (notice.DataviewNotice?.UpdateViewNotice != undefined) {
                dataViewStore.projectStore.loadViewInfo();
            } else if (notice.DataviewNotice?.CreateNodeNotice != undefined) {
                dataViewStore.projectStore.onUpdateNode(notice.DataviewNotice.CreateNodeNotice.node_id);
            } else if (notice.DataviewNotice?.UpdateNodeNotice != undefined) {
                dataViewStore.projectStore.onUpdateNode(notice.DataviewNotice.UpdateNodeNotice.node_id);
            } else if (notice.DataviewNotice?.RemoveNodeNotice != undefined) {
                dataViewStore.projectStore.onRemoveNode(notice.DataviewNotice.RemoveNodeNotice.node_id);
            } else if (notice.DataviewNotice?.CreateEdgeNotice != undefined) {
                dataViewStore.projectStore.onUpdateEdge(notice.DataviewNotice.CreateEdgeNotice.edge_key);
            } else if (notice.DataviewNotice?.UpdateEdgeNotice != undefined) {
                dataViewStore.projectStore.onUpdateEdge(notice.DataviewNotice.UpdateEdgeNotice.edge_key);
            } else if (notice.DataviewNotice?.RemoveEdgeNotice != undefined) {
                dataViewStore.projectStore.onRemoveEdge(notice.DataviewNotice.RemoveEdgeNotice.edge_key);
            } else if (notice.ProjectNotice?.UpdateProjectNotice != undefined && notice.ProjectNotice.UpdateProjectNotice.project_id == dataViewStore.projectStore.projectId) {
                dataViewStore.projectStore.loadProject();
            } else if (notice.ProjectNotice?.UpdateMemberNotice != undefined && notice.ProjectNotice.UpdateMemberNotice.project_id == dataViewStore.projectStore.projectId && notice.ProjectNotice.UpdateMemberNotice.member_user_id == dataViewStore.projectStore.myUserId) {
                dataViewStore.projectStore.loadMemberList();
            }
        });
        return () => {
            unListenFn.then((unListen) => unListen());
        };
    }, []);

    return (
        <ReactFlowProvider>
            <div ref={divRef} style={{ width: "100vw", height: "100vh" }}>
                <ReactFlow ref={drop} onInit={(instance) => dataViewStore.projectStore.flowInstance = instance}
                    deleteKeyCode={null} onNodesChange={onNodesChange} onConnect={onConnect} connectionMode={ConnectionMode.Loose}
                    nodes={nodes} nodeTypes={allNodeTyps}
                    edges={edges} edgeTypes={{
                        LabelEdge: LabelEdge,
                    }}
                    panOnScroll
                    fitView>
                    <Background color="#777" style={{ backgroundColor: "#fafafa" }} />
                    <Controls position='top-right' showInteractive={dataViewStore.projectStore.viewInfo?.user_perm.can_update ?? false} />
                    {(dataViewStore.projectStore.viewInfo?.user_perm.can_update ?? false) == true && (
                        <Panel position="top-left">
                            <ProjectNodeListPanel projectId={dataViewStore.projectStore.projectId} />
                        </Panel>
                    )}
                    {(dataViewStore.projectStore.viewInfo?.user_perm.can_update_perm ?? false) == true && (
                        <Panel position="bottom-left">
                            <ProjectMemberListPanel />
                        </Panel>
                    )}
                    <Panel position='bottom-right'>
                        <ChatPanel />
                    </Panel>
                </ReactFlow>
            </div>
        </ReactFlowProvider>
    );
});

const ProjectDataViewBoard = () => {
    return (
        <DndProvider backend={HTML5Backend}>
            <ProjectDataViewEditor />
        </DndProvider>
    );
};

export default ProjectDataViewBoard;